/**
 * 2025/01/01 - 2025/12/31
 */
package com.snail.server.iam.user.service;

import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.snail.server.database.engine.datasource.holder.DynamicDatasourceHolder;
import com.snail.server.iam.account.entity.Account;
import com.snail.server.iam.app.entity.App;
import com.snail.server.iam.error.IamErrorCode;
import com.snail.server.iam.user.entity.UserAppMapping;
import com.snail.server.iam.user.entity.User;
import com.snail.server.iam.user.enums.UserTypeEnum;
import com.snail.server.iam.user.mapper.UserAppMappingMapper;
import com.snail.server.iam.user.mapper.UserMapper;
import com.snail.starter.commons.exception.BizException;
import com.snail.starter.commons.threadlocal.UserContext;
import com.snail.starter.commons.threadlocal.UserContextUtils;
import com.snail.starter.db.enums.EnableOrDisEnableEnum;
import com.snail.starter.db.holder.DynamicTransactionTemplate;
import com.snail.starter.iam.authentication.local.token.ITokenService;
import com.snail.starter.utils.lambda.GenericEntityBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author zgadmin
 * @date 2025/9/24 18:53
 */
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    @Resource
    UserAppMappingMapper tenantUserAppMappingMapper;

    @Resource
    DynamicTransactionTemplate dynamicTransactionTemplate;

    @Resource
    ITokenService tokenService;

    public UserContext login(Long tenantId, App app, Account account, UserTypeEnum userType) {
        User user = checkByAccountAndAppKey(account.getAccountId(), userType);
        UserAppMapping tenantUser = checkUserTenant(tenantId, user.getUserId(), app.getAppId());
        UserContext userContext = generateUserContext(tenantUser, app, user, account, UserContextUtils.get());
        dynamicTransactionTemplate.doTransaction(() -> {
            tenantUserAppMappingMapper.updateById(GenericEntityBuilder.of(UserAppMapping::new)
                    .withSupplier(UserAppMapping::setId, tenantUser::getId)
                    .withSupplier(UserAppMapping::setLastLoginTime, userContext::getLoginTime)
                    .build());
            String token = tokenService.generate(userContext);
            userContext.setToken(token);
        });
        return userContext;
    }

    public User checkByAccountAndAppKey(Long accountId, UserTypeEnum userType) {
        User user;
        try {
            InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
            user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getAccountId, accountId).eq(User::getUserType, userType));
        } finally {
            InterceptorIgnoreHelper.clearIgnoreStrategy();
        }
        if (null == user) {
            throw BizException.of(IamErrorCode.USER_NOT_EXISTS, new Object[]{userType.getValue()});
        }
        if (!EnableOrDisEnableEnum.ENABLE.equals(user.getStatus())) {
            throw BizException.of(IamErrorCode.USER_DISABLE);
        }
        return user;
    }

    public UserAppMapping checkUserTenant(Long tenantId, Long userId, Long appId) {
        try {
            InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
            List<UserAppMapping> userAppMappings = tenantUserAppMappingMapper.selectList(Wrappers.lambdaQuery(UserAppMapping.class)
                    .eq(UserAppMapping::getUserId, userId)
                    .eq(UserAppMapping::getAppId, appId)
                    .eq(null != tenantId && !tenantId.equals(UserContext.defaultContext().getTenantId()), UserAppMapping::getTenantId, tenantId)
                    .orderByDesc(UserAppMapping::getLastLoginTime));
            if (null == userAppMappings || userAppMappings.isEmpty()) {
                throw BizException.of(IamErrorCode.USER_AUTHORIZATION_ERROR, new Object[]{userId});
            }
            return userAppMappings.stream()
                    .filter(userAppMapping1 -> EnableOrDisEnableEnum.ENABLE.equals(userAppMapping1.getStatus()))
                    .findFirst().orElseThrow(() -> BizException.of(IamErrorCode.USER_DISABLE, new Object[]{userId}));
        } finally {
            InterceptorIgnoreHelper.clearIgnoreStrategy();
        }
    }

    private UserContext generateUserContext(UserAppMapping tenantUser, App app, User user, Account account, UserContext defaultUserContext) {
        return GenericEntityBuilder
                .of(UserContext::new)
                .withSupplier(UserContext::setAccountId, account::getAccountId)
                .withSupplier(UserContext::setUserId, user::getUserId)
                .with(UserContext::setAppId, app.getAppId())
                .with(UserContext::setAdmin, tenantUser.getAdmin().getBoolean())
                .with(UserContext::setSuperAdmin, tenantUser.getSuperAdmin().getBoolean())
                .withSupplier(UserContext::setMobile, user::getMobile)
                .withSupplier(UserContext::setName, user::getName)
                .withSupplier(UserContext::setUserName, account::getLoginName)
                .with(UserContext::setTenantId, tenantUser.getTenantId())
                .with(UserContext::setToken, null)
                .with(UserContext::setVersion, null)
                .with(UserContext::setAppKey, app.getAppKey())
                .with(UserContext::setLoginTime, System.currentTimeMillis())
                .with(UserContext::setDefault, false)
                .with(UserContext::setBasic, false)
                .with(UserContext::setLocale, defaultUserContext.getLocale())
                .with(UserContext::setTraceId, defaultUserContext.getTraceId())
                .with(UserContext::setSpanId, defaultUserContext.getSpanId())
                .with(UserContext::setMaxSession, tenantUser.getMaxSession())
                .build();
    }

}